// standard libraries
#include <string>
#include <cstdlib>
#include <vector>

// custom libraries
#include "..\include\audio\sound.h"
#include "..\include\file\infile.h"
#include "..\include\graphics\font.h"
#include "..\include\graphics\image.h"
#include "..\include\logger.h"
#include "..\include\resource_manager.h" // matching header


// ==================================
//        STRUCT DEFINITION
// ==================================

// Structs used for encompassing information about a given resource. 
// Information is then used when loading or deleting the resource, to make
// sure the same resource isn't loaded twice, or deleted while still in use.

struct ResourceManager::FontResource
{
	// If loaded from file
	std::string path;

	// If loaded from buffer
	const char* buffer;
	unsigned int size;

	// Number of places resource is used
	int nUses;

	// Actual data
	Font* data;
};

struct ResourceManager::ImageResource
{
	// If loaded from file
	std::string path;

	// If loaded from buffer
	const char* buffer;
	unsigned int size;

	// Number of places resource is used
	int nUses;

	// Actual data
	Image* data;
};

struct ResourceManager::SoundResource
{
	// If loaded from file
	std::string path;

	// If loaded from buffer
	const char* buffer;
	unsigned int size;

	// Number of places resource is needed
	int nUses;

	// Actual data
	Sound* data;
};


// ==================================
//     SINGLETON IMPLEMENTATION
// ==================================

ResourceManager* ResourceManager::Instance()
{
	// Meyers singleton
	static ResourceManager obj;
	return &obj;
}


ResourceManager::~ResourceManager() 
{
#warning TODO: implement releaseAll()
	// releaseAll();
}

ResourceManager::ResourceManager() {}
ResourceManager::ResourceManager(const ResourceManager& other) {}
ResourceManager& ResourceManager::operator=(const ResourceManager& other) { 
	return *this; }


// ==================================
//       ACCESSING RESOURCES
// ==================================

Font* ResourceManager::getFont(const char* buffer, unsigned int size)
{
	Font* font = 0;

	if ((buffer != 0) || (size != 0)) {
		FontResource* res = findFontResource(buffer, size);
		
		if (res != 0) {
			font = res->data;
			res->nUses++;
		} else {
			res = createFontResource(buffer, size);
			font = res->data;
			res->nUses = 1;
			fonts_.push_back(res);
		}
	}

	return font;
}

Font* ResourceManager::getFont(std::string filepath)
{
	Font* font = 0;

	FontResource* res = findFontResource(filepath);
	if (res != 0) {
		font = res->data;
		res->nUses++;
	} else {
		InFile file(filepath);
		
		if (file.is_open()) {
			res = createFontResource(file, filepath);
			font = res->data;
			res->nUses = 1;
			fonts_.push_back(res);
		} else {
			Logger::Instance()->addEntry(Logger::ERROR,
				"ResourceManager: Cannot open file '" + filepath + "'.");
		}
	}

	return font;
}


Image* ResourceManager::getImage(const char* buffer, unsigned int size)
{
	Image* img = 0;

	if ((buffer != 0) || (size != 0)) {
		ImageResource* res = findImageResource(buffer, size);
		
		if (res != 0) {
			img = res->data;
			res->nUses++;
		} else {
			res = createImageResource(buffer, size);
			img = res->data;
			res->nUses = 1;
			images_.push_back(res);
		}
	}

	return img;
}

Image* ResourceManager::getImage(std::string filepath)
{
	Image* img = 0;

	ImageResource* res = findImageResource(filepath);
	if (res != 0) {
		img = res->data;
		res->nUses++;
	} else {
		InFile file(filepath);
		
		if (file.is_open()) {
			res = createImageResource(file, filepath);
			img = res->data;
			res->nUses = 1;
			images_.push_back(res);
		} else {
			Logger::Instance()->addEntry(Logger::ERROR,
				"ResourceManager: Cannot open file '" + filepath + ".");
		}
	}

	return img;
}


Sound* ResourceManager::getSound(const char* buffer, unsigned int size)
{
	Sound* sound = 0;
#warning TODO: Implement getSound(buffer).
	return sound;
}

Sound* ResourceManager::getSound(std::string filepath)
{
	Sound* sound = 0;
#warning TODO: Implement getSound(file).
	return sound;
}


// ==================================
//        RELEASING RESOURCES
// ==================================

void ResourceManager::release(Font* font)
{
	FontResource* res = 0;
	int it = 0;

	for (int s = fonts_.size(); it < s; it++) {
		if (fonts_[it]->data == font) {
			res = fonts_[it];
			break;
		}
	}

	if (res != 0) {
		if (--(res->nUses) == 0) {
			delete res->data;
			delete res;
			fonts_.erase(fonts_.begin() + it);
		}
	}
}

void ResourceManager::release(Image* image)
{
	ImageResource* res = 0;
	int it = 0;

	for (int s = images_.size(); it < s; it++) {
		if (images_[it]->data == image) {
			res = images_[it];
			break;
		}
	}

	if (res != 0) {
		if (--(res->nUses) == 0) {
			delete res->data;
			delete res;
			images_.erase(images_.begin() + it);
		}
	}
}

void ResourceManager::release(Sound* sound)
{
	SoundResource* res = 0;
	int it = 0;

	for (int s = sounds_.size(); it < s; it++) {
		if (sounds_[it]->data == sound) {
			res = sounds_[it];
			break;
		}
	}

	if (res != 0) {
		if (--(res->nUses) == 0) {
			delete res->data;
			delete res;
			sounds_.erase(sounds_.begin() + it);
		}
	}
}


// ==================================
//        CREATING RESOURCES
// ==================================

ResourceManager::FontResource* ResourceManager::createFontResource(
	const char* buffer, unsigned int size)
{
	FontResource* res = new FontResource;
	Font* font = new Font(buffer, size);

	res->data   = font;
	res->buffer = buffer;
	res->size   = size;
	res->nUses  = 0;

	return res;
}

ResourceManager::FontResource* ResourceManager::createFontResource(
	InFile& file, std::string filepath)
{
	file.seekg(0, FileBase::end);
	size_t size = file.tellg();
	file.seekg(0, FileBase::beg);
	
	char* buffer = (char*)malloc(sizeof(char) * size);
	file.read(buffer, size);
	
	FontResource* res = new FontResource;
	Font* font = new Font(buffer, (unsigned int)size);
	//free(buffer);

	res->data  = font;
	res->path  = filepath;
	res->nUses = 0;

	return res;
}


ResourceManager::ImageResource* ResourceManager::createImageResource(
	const char* buffer, unsigned int size)
{
	ImageResource* res = new ImageResource;
	Image* img = new Image(buffer, size);

	res->data   = img;
	res->buffer = buffer;
	res->size   = size;
	res->nUses  = 0;

	return res;
}

ResourceManager::ImageResource* ResourceManager::createImageResource(
	InFile& file, std::string filepath)
{
	file.seekg(0, FileBase::end);
	size_t size = file.tellg();
	file.seekg(0, FileBase::beg);
	
	char* buffer = (char*)malloc(sizeof(char) * size);
	file.read(buffer, size);
	
	ImageResource* res = new ImageResource;
	Image* img = new Image(buffer, (unsigned int)size);
	free(buffer);

	res->data  = img;
	res->path  = filepath;
	res->nUses = 0;

	return res;
}


ResourceManager::SoundResource* ResourceManager::createSoundResource(
	const char* buffer, unsigned int size)
{
	SoundResource* res = 0;
#warning TODO: Implement createSoundResource(buffer).
	return res;
}

ResourceManager::SoundResource* ResourceManager::createSoundResource(
	InFile& file, std::string filepath)
{
	SoundResource* res = 0;
#warning TODO: Implement createSoundResource(file).
	return res;
}


// ==================================
//     FINDING LOADED RESOURCES
// ==================================

ResourceManager::FontResource* ResourceManager::findFontResource(
	const char* buffer, unsigned int size)
{
	FontResource* res = 0;

	for (int i = 0, s = fonts_.size(); i < s; i++) {
		if ((fonts_[i]->buffer == buffer) && (fonts_[i]->size == size)) {
			res = fonts_[i];
			break;
		}
	}

	return res;
}

ResourceManager::FontResource* ResourceManager::findFontResource(
	std::string filepath)
{
	FontResource* res = 0;

	for (int i = 0, s = fonts_.size(); i < s; i++) {
		if (fonts_[i]->path == filepath) {
			res = fonts_[i];
			break;
		}
	}

	return res;
}


ResourceManager::ImageResource* ResourceManager::findImageResource(
	const char* buffer, unsigned int size)
{
	ImageResource* res = 0;

	for (int i = 0, s = images_.size(); i < s; i++) {
		if ((images_[i]->buffer == buffer) && (images_[i]->size == size)) {
			res = images_[i];
			break;
		}
	}

	return res;
}

ResourceManager::ImageResource* ResourceManager::findImageResource(
	std::string filepath)
{
	ImageResource* res = 0;

	for (int i = 0, s = images_.size(); i < s; i++) {
		if (images_[i]->path == filepath) {
			res = images_[i];
			break;
		}
	}

	return res;
}


ResourceManager::SoundResource* ResourceManager::findSoundResource(
	const char* buffer, unsigned int size)
{
	SoundResource* res = 0;
#warning TODO: Implement findSoundResource(buffer).
	return res;
}

ResourceManager::SoundResource* ResourceManager::findSoundResource(
	std::string filepath)
{
	SoundResource* res = 0;
#warning TODO: Implement findSoundResource(file).
	return res;
}
